﻿using System;
using System.Linq;
using System.Text;
using System.Data.Linq;
using System.Collections.Generic;
using System.Linq.Expressions;

using LeanEngine.Data.Linq.Language;
using LeanEngine.Data.Linq.Builder;

namespace LeanEngine.Data.Linq.Translator
{
    internal sealed class QueryTranslator : LinqTranslator<QueryInfo>
    {
        #region Declaration Members

        private GroupbyTranslator   _groupTranslator;
        private OrderbyTranslator   _orderTranslator;
        private WhereTranslator     _whereTranslator;
        private JoinTranslator      _joinTranslator;
        private SelectTranslator    _selectTranslator;
        private FromTranslator      _fromTranslator;
        private TakeTranslator      _takeTranslator;

        private LinqQuery           _linqQuery;

        private QueryContext        _context;

        #endregion
        
        #region Method Members

        /// <summary>
        /// What will this query return?
        /// - a single entity?
        /// - a sequence of entities?
        /// - or nothing?
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        private ResultShape GetResultShape(Expression query)
        {
            if ((_linqQuery.Select as SelectToken).Count)
            {
                return ResultShape.SingleValue;
            }

            LambdaExpression lambda = query as LambdaExpression;
            if (lambda != null)
                query = lambda.Body;

            if (query.Type == typeof(void))
                return ResultShape.None;

            if (query.Type == typeof(IMultipleResults))
                throw new NotSupportedException("Multiple result shape is not supported");

            MethodCallExpression methodExp = query as MethodCallExpression;
            if (methodExp != null && ((methodExp.Method.DeclaringType == typeof(Queryable)) || (methodExp.Method.DeclaringType == typeof(Enumerable))))
            {
                string str = methodExp.Method.Name;
                if (str != null && (str == "First" || str == "FirstOrDefault" || str == "Single" || str == "SingleOrDefault"))
                    return ResultShape.Singleton;
            }

            return ResultShape.Sequence;
        }

        #endregion

        #region Overrided Method Members

        protected override Expression EvaluateMethodCall(MethodCallExpression currentMethod)
        {
            Type declaringType = currentMethod.Method.DeclaringType;
            if (declaringType != typeof(Queryable) && 
                declaringType != typeof(PagingExtensions)) 
                throw new NotSupportedException("Invalid Sequence Operator Call. The type for the operator is not Queryable!");

            _context.CurrentMethodCall = currentMethod;
            
            if (_selectTranslator.CanEvaluate(_context))
            {
                _linqQuery.Select = _selectTranslator.Interpret(_context);
            }
            else if (_takeTranslator.CanEvaluate(_context))
            {
                _linqQuery.Take = _takeTranslator.Interpret(_context);
            }
            else if (_joinTranslator.CanEvaluate(_context))
            {
                _linqQuery.JoinList = _joinTranslator.Interpret(_context).ToArray();
            }
            else if (_whereTranslator.CanEvaluate(_context))
            {
                _linqQuery.Where = _whereTranslator.Interpret(_context);
            }
            else if (_orderTranslator.CanEvaluate(_context))
            {
                _linqQuery.OrderBy = _orderTranslator.Interpret(_context);
            }
            else if (_groupTranslator.CanEvaluate(_context))
            {
                _linqQuery.GroupBy = _groupTranslator.Interpret(_context);
            }

            base.Evaluate(currentMethod.Arguments[0]);

            return currentMethod;
        }

        #endregion

        #region LinqTranslator<QueryInfo> Members

        internal override bool CanEvaluate(QueryContext context)
        {
            return true;
        }

        internal override QueryInfo Interpret(QueryContext context)
        {
            _context = context;
            _linqQuery = new LinqQuery();
            _groupTranslator = new GroupbyTranslator();
            _takeTranslator = new TakeTranslator();
            _orderTranslator = new OrderbyTranslator();
            _whereTranslator = new WhereTranslator();
            _joinTranslator = new JoinTranslator();
            _fromTranslator = new FromTranslator();

            var table = _fromTranslator.Interpret(context);
            _linqQuery.From = table;
            context.QueryableTableList.Add(table.Table);
            _selectTranslator = new SelectTranslator(table.Table);

            this.Evaluate(context.LinqExpression);

            if (_linqQuery.Select == null)
            {
                _linqQuery.Select = _selectTranslator.Interpret(context);
            }

            if (_joinTranslator.BindingsList != null)
            {
                _selectTranslator.TranslateCustomBinding(_joinTranslator.BindingsList);
            }

            var builder = QueryBuilderFacade.GetQueryBuilder(_linqQuery.Select as SelectToken);
            var tsql = builder.BuildTSQL(_linqQuery);

            return new QueryInfo(GetResultShape(context.LinqExpression), tsql)
            {
                SourceMetadata = table.Table.Metadata,
                LambdaExpression = _selectTranslator.Projection,
                UseDefault = _takeTranslator.UseDefault,
                CustomBindings = _selectTranslator.CustomBindings
            };
        }

        #endregion
    }
}